////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNMemory/inc/WNMemory.h"
#include "WNMath/inc/WNBounds3.h"
#include "WNMath/inc/WNBasic.h"
#include "WNMath/inc/WNExtended.h"
#include "WNMath/inc/WNBox.h"
#include "WNMath/inc/WNSphere.h"
#include "WNMath/inc/WNVector3.h"

using namespace WNMath;
using namespace WNMemory;

// Constructors ////////////////////////////////////////////////////////////////////////////////////

WNBounds3::WNBounds3() :
    min(0.0f, 0.0f, 0.0f),
    max(0.0f, 0.0f, 0.0f) {
}

WNBounds3::WNBounds3(const WN_FLOAT32* _f) :
    min(_f[0], _f[1], _f[2]),
    max(_f[3], _f[4], _f[5]) {
}

WNBounds3::WNBounds3(const WNPoint3& _point) :
    min(_point),
    max(_point) {
}

WNBounds3::WNBounds3(const WNPoint3& _min, const WNPoint3& _max) :
    min(_min),
    max(_max) {
}

// Operators ///////////////////////////////////////////////////////////////////////////////////////

WN_BOOL WNBounds3::operator == (const WNBounds3& _bounds) const {
    return(0 == WNMemCmp(this, &_bounds, sizeof(WNBounds3)));
}

WN_BOOL WNBounds3::operator != (const WNBounds3& _bounds) const {
    return(0 != WNMemCmp(this, &_bounds, sizeof(WNBounds3)));
}

// Members /////////////////////////////////////////////////////////////////////////////////////////

WN_VOID WNBounds3::Zero() {
    min.Zero();
    max.Zero();
}

WN_FLOAT32 WNBounds3::Length() const {
    const WNVector3 size = max - min;

    return(WNAbs(size.x));
}

WN_FLOAT32 WNBounds3::Width() const {
    const WNVector3 size = max - min;

    return(WNAbs(size.z));
}

WN_FLOAT32 WNBounds3::Height() const {
    const WNVector3 size = max - min;

    return(WNAbs(size.y));
}

WN_FLOAT32 WNBounds3::Radius() const {
    WN_FLOAT32 total = 0.0f;

    for (WN_INT32 i = 0; i < 3; i++) {
        const WN_FLOAT32 minComponent = WNAbs(min[i]);
        const WN_FLOAT32 maxComponent = WNAbs(max[i]);

        if (minComponent > maxComponent) {
            total += minComponent * minComponent;
        } else {
            total += maxComponent * maxComponent;
        }
    }

    return(WNSqrt(total));
}

WN_FLOAT32 WNBounds3::SurfaceArea() const {
    const WNVector3 size = max - min;
    const WN_FLOAT32 xy = WNAbs(size.x * size.y);
    const WN_FLOAT32 xz = WNAbs(size.x * size.z);
    const WN_FLOAT32 yz = WNAbs(size.y * size.z);

    return(xy + xz + yz);
}

WN_FLOAT32 WNBounds3::Volume() const {
    const WNVector3 size = max - min;

    return(WNAbs(size.x * size.y * size.z));
}

WN_BOOL WNBounds3::IsZero() const {
    return(min.IsZero() && max.IsZero());
}

WN_BOOL WNBounds3::IsInsideOut() const {
    return(min.x > max.x || min.y > max.y || min.z > max.z);
}

WN_VOID WNBounds3::Expand(WN_FLOAT32 _amount) {
    const WNVector3 expand = WNVector3(_amount);

    max += expand;
    min -= expand;
}

WN_BOOL WNBounds3::Expand(const WNPoint3& _point) {
    WN_BOOL expanded = WN_FALSE;

    if (_point.x < min.x) {
        min.x = _point.x;

        expanded = WN_TRUE;
    }

    if (_point.x > max.x) {
        max.x = _point.x;

        expanded = WN_TRUE;
    }

    if (_point.y < min.y) {
        min.y = _point.y;

        expanded = WN_TRUE;
    }

    if (_point.y > max.y) {
        max.y = _point.y;

        expanded = WN_TRUE;
    }

    if (_point.z < min.z) {
        min.z = _point.z;

        expanded = WN_TRUE;
    }

    if (_point.z > max.z) {
        max.z = _point.z;

        expanded = WN_TRUE;
    }

    return(expanded);
}

WN_BOOL WNBounds3::Expand(const WNBox& _box) {
    const WNVector3 extents = _box.extents.GetRotated(_box.rotation);
    const WNPoint3 point1 = _box.location + extents;
    const WNPoint3 point2 = _box.location + WNVector3(-extents.x, extents.y, extents.z);
    const WNPoint3 point3 = _box.location + WNVector3(extents.x, -extents.y, extents.z);
    const WNPoint3 point4 = _box.location + WNVector3(extents.x, extents.y, -extents.z);
    const WNPoint3 point5 = _box.location + WNVector3(-extents.x, -extents.y, extents.z);
    const WNPoint3 point6 = _box.location + WNVector3(extents.x, -extents.y, -extents.z);
    const WNPoint3 point7 = _box.location + WNVector3(-extents.x, extents.y, -extents.z);
    const WNPoint3 point8 = _box.location - extents;

    return(Expand(point1) || Expand(point2) || Expand(point3) || Expand(point4) ||
           Expand(point5) || Expand(point6) || Expand(point7) || Expand(point8));
}

WN_BOOL WNBounds3::Expand(const WNSphere& _sphere) {
    const WNPoint3 location = _sphere.location;
    const WN_FLOAT32 radius = _sphere.radius;

    return(Expand(WNPoint3(location.x + radius, location.y, location.z)) ||
           Expand(WNPoint3(location.x - radius, location.y, location.z)) ||
           Expand(WNPoint3(location.x, location.y + radius, location.z)) ||
           Expand(WNPoint3(location.x, location.y - radius, location.z)) ||
           Expand(WNPoint3(location.x, location.y, location.z + radius)) ||
           Expand(WNPoint3(location.x, location.y, location.z - radius)));
}

WN_BOOL WNBounds3::Expand(const WNBounds3& _bounds) {
    WN_BOOL expanded = WN_FALSE;

    if (_bounds.min.x < min.x) {
        min.x = _bounds.min.x;

        expanded = WN_TRUE;
    }

    if (_bounds.min.y < min.y) {
        min.y = _bounds.min.y;

        expanded = WN_TRUE;
    }

    if (_bounds.min.z < min.z) {
        min.z = _bounds.min.z;

        expanded = WN_TRUE;
    }

    if (_bounds.max.x > max.x) {
        max.x = _bounds.max.x;

        expanded = WN_TRUE;
    }

    if (_bounds.max.y > max.y) {
        max.y = _bounds.max.y;

        expanded = WN_TRUE;
    }

    if (_bounds.max.z > max.z) {
        max.z = _bounds.max.z;

        expanded = WN_TRUE;
    }

    return(expanded);
}

WN_VOID WNBounds3::Translate(const WNVector3& _vector) {
    min += _vector;
    max += _vector;
}

WNBounds3 WNBounds3::GetExpanded(WN_FLOAT32 _amount) const {
    WNBounds3 bounds = *this;

    return(bounds.Expand(_amount), bounds);
}

WNBounds3 WNBounds3::GetExpanded(const WNPoint3& _point) const {
    WNBounds3 bounds = *this;

    return(bounds.Expand(_point), bounds);
}

WNBounds3 WNBounds3::GetExpanded(const WNBox& _box) const {
    WNBounds3 bounds = *this;

    return(bounds.Expand(_box), bounds);
}

WNBounds3 WNBounds3::GetExpanded(const WNSphere& _sphere) const {
    WNBounds3 bounds = *this;

    return(bounds.Expand(_sphere), bounds);
}

WNBounds3 WNBounds3::GetExpanded(const WNBounds3& _bounds) const {
    WNBounds3 bounds = *this;

    return(bounds.Expand(_bounds), bounds);
}

WNBounds3 WNBounds3::GetTranslated(const WNVector3& _vector) const {
    WNBounds3 bounds = *this;

    return(bounds.Translate(_vector), bounds);
}

WN_VOID WNBounds3::GetPoints(WNPoint3* _points) const {
    _points[0] = max;
    _points[6] = min;
    _points[1] = (min * WNPoint3(0.0f, 1.0f, 0.0f) + max * WNPoint3(1.0f, 0.0f, 1.0f)).ToPoint3();
    _points[2] = (min * WNPoint3(1.0f, 1.0f, 0.0f) + max * WNPoint3(0.0f, 0.0f, 1.0f)).ToPoint3();
    _points[3] = (min * WNPoint3(1.0f, 0.0f, 0.0f) + max * WNPoint3(0.0f, 1.0f, 1.0f)).ToPoint3();
    _points[4] = (min * WNPoint3(0.0f, 0.0f, 1.0f) + max * WNPoint3(1.0f, 1.0f, 0.0f)).ToPoint3();
    _points[5] = (min * WNPoint3(0.0f, 1.0f, 1.0f) + max * WNPoint3(1.0f, 0.0f, 0.0f)).ToPoint3();
    _points[7] = (min * WNPoint3(1.0f, 0.0f, 1.0f) + max * WNPoint3(1.0f, 1.0f, 0.0f)).ToPoint3();
}

WNBox WNBounds3::ToBox() const {
    const WNPoint3 location = WNMidPoint(min, max);
    const WNVector3 extents = max - location;

    return(WNBox(location, extents, WNQuaternion()));
}